use serde::{ ser::Error, Deserialize, Serialize };
use serde_json;
use ts_rs::TS;

/// 持仓数据
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
#[ts(export)]
pub enum TradeHoldingData {
    Money(Money),
    Share(Share),
}

/// 持仓金额
#[derive(Serialize, Deserialize, Debug, Clone, Default, TS)]
#[ts(export)]
pub struct Money {
    ///人民币CNY, 港币HKD, 美元USD
    pub currency: String,
    /// 总资产
    pub total: f32,
    /// 可用资金
    pub available: f32,
    /// 证券资产
    pub asset_total: f32,
    /// 当日参考盈亏
    pub toady_profit: f32,
    /// 持仓盈亏
    pub holding_profit: f32,
}

/// 持仓股份
#[derive(Serialize, Deserialize, Debug, Clone, Default, TS)]
#[ts(export)]
pub struct Share {
    /// 证券名称，采自《国信证券》持仓记录详情
    pub security_name: String,
    /// 证券代码，采自《国信证券》持仓记录详情, using TradeHolding.security_code
    // pub security_code: String,


    /// 持仓盈亏金额，采自《国信证券》持仓记录详情
    pub profit_loss_amount: f64,
    /// 仓位
    pub holding_percentage: f64,
    /// 盈亏百分比，采自《国信证券》持仓记录详情
    pub profit_loss_percentage: f64,
    /// 当前市值，采自《国信证券》持仓记录详情
    pub market_value: f64,
    /// 现价，采自《国信证券》持仓记录详情
    pub current_price: f64,
    /// 成本基础，采自《国信证券》持仓记录详情
    pub cost_basis: f64,
    /// 持股数，采自《国信证券》持仓记录详情
    pub shares_held: u32,
    /// 可用股份数，采自《国信证券》持仓记录详情
    pub available_shares: u32,
}

impl TradeHoldingData {
    pub fn tname(&self) -> &str {
        match self {
            TradeHoldingData::Money(_) => "Money",
            TradeHoldingData::Share(_) => "Share",
        }
    }
    pub fn serialize(&self) -> Result<String, serde_json::Error> {
        let mut map = serde_json::Map::new();
        map.insert(
            "type".into(),
            serde_json::Value::String(
                format!("com.fuda.trade.model.TradeHoldingData.{}", self.tname())
            )
        );

        match self {
            TradeHoldingData::Money(data) => {
                let data_map = serde_json::to_value(data)?;
                map.extend(data_map.as_object().unwrap().clone());
            }
            TradeHoldingData::Share(data) => {
                let data_map = serde_json::to_value(data)?;
                map.extend(data_map.as_object().unwrap().clone());
            }
        }

        serde_json::to_string(&map)
    }

    pub fn deserialize(json: &str) -> Result<TradeHoldingData, serde_json::Error> {
        let value: serde_json::Value = serde_json::from_str(json)?;
        let type_str = value["type"]
            .as_str()
            .ok_or_else(|| serde_json::Error::custom("Missing type field"))?;

        match type_str {
            "com.fuda.trade.model.TradeHoldingData.Money" => {
                let buy_auto_value = value
                    .as_object()
                    .ok_or_else(|| serde_json::Error::custom("Expected object"))?;
                Ok(
                    TradeHoldingData::Money(
                        serde_json::from_value(serde_json::Value::Object(buy_auto_value.clone()))?
                    )
                )
            }
            "com.fuda.trade.model.TradeHoldingData.Share" => {
                let sell_auto_value = value
                    .as_object()
                    .ok_or_else(|| serde_json::Error::custom("Expected object"))?;
                Ok(
                    TradeHoldingData::Share(
                        serde_json::from_value(serde_json::Value::Object(sell_auto_value.clone()))?
                    )
                )
            }

            _ => Err(serde_json::Error::custom("Unknown type")),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use log::debug;

    #[tokio::test]
    async fn test_trade_holding_data_serde() {
        pretty_env_logger::init_timed();
        debug!("debug No more records available; exit the loop");
        println!("print No more records available; exit the loop");

        let money = TradeHoldingData::Money(Money {
            currency: "CNY".to_string(),
            total: 1000.0,
            available: 200.0,
            asset_total: 500.0,
            toady_profit: 300.0,
            holding_profit: 600.0,
        });

        let json = money.serialize().unwrap();
        // {"type":"com.fuda.trade.model.TradeHoldingData.Money","securityName":"贵州茅台","securityCode":"SH.STK.600519","price":160.8,"volume":100,"desc":"Publish1@TradeHoldingDataUnitTest.kt"}
        // {"type":"com.fuda.trade.model.TradeHoldingData.Money","securityName":"贵州茅台","securityCode":"SH.STK.600519","price":500.0,"volume":500,"desc":"New command added"}
        println!("serialized json: \n{}", json);

        // Deserialize the JSON back into a TradeHoldingData
        let buy_auto_de: TradeHoldingData = TradeHoldingData::deserialize(&json).unwrap();

        // Check that the deserialized TradeHoldingData matches the original
        match (money, buy_auto_de) {
            (TradeHoldingData::Money(original), TradeHoldingData::Money(deserialized)) => {
                assert_eq!(original.currency, deserialized.currency);
                assert_eq!(original.total, deserialized.total);
                assert_eq!(original.available, deserialized.available);
                assert_eq!(original.asset_total, deserialized.asset_total);
                assert_eq!(original.toady_profit, deserialized.toady_profit);
            }
            _ => panic!("Deserialized TradeHoldingData does not match the original"),
        }
    }
}
